/** @file
  Process Capsule On Disk.

  Copyright (c) 2019, Intel Corporation. All rights reserved.<BR>
  This program and the accompanying materials
  are licensed and made available under the terms and conditions of the BSD License
  which accompanies this distribution.  The full text of the license may be found at
  http://opensource.org/licenses/bsd-license.php

  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/
#include <Uefi.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Library/PrintLib.h>
#include <Library/DevicePathLib.h>
#include <Library/FileHandleLib.h>
#include <Library/UefiBootManagerLib.h>
#include <Protocol/SimpleFileSystem.h>
#include <Protocol/Shell.h>
#include <Guid/FileInfo.h>
#include <Guid/GlobalVariable.h>
#include <Guid/Gpt.h>

EFI_GUID mCapsuleOnDiskBootOptionGuid = { 0x4CC29BB7, 0x2413, 0x40A2, { 0xB0, 0x6D, 0x25, 0x3E, 0x37, 0x10, 0xF5, 0x32 } };

/**
  Get shell protocol.

  @return Pointer to shell protocol.

**/
EFI_SHELL_PROTOCOL *
GetShellProtocol (
  VOID
  );

/**
  Get file name from file path.

  @param  FilePath    File path.

  @return Pointer to file name.

**/
CHAR16 *
GetFileNameFromPath (
  CHAR16                            *FilePath
  )
{
  EFI_STATUS                        Status;
  EFI_SHELL_PROTOCOL                *ShellProtocol;
  SHELL_FILE_HANDLE                 Handle;
  EFI_FILE_INFO                     *FileInfo;

  ShellProtocol = GetShellProtocol ();
  if (ShellProtocol == NULL) {
    return NULL;
  }

  //
  // Open file by FileName.
  //
  Status = ShellProtocol->OpenFileByName (
                            FilePath,
                            &Handle,
                            EFI_FILE_MODE_READ
                            );
  if (EFI_ERROR (Status)) {
    return NULL;
  }

  //
  // Get file name from EFI_FILE_INFO.
  //
  FileInfo = ShellProtocol->GetFileInfo (Handle);
  ShellProtocol->CloseFile (Handle);
  if (FileInfo == NULL) {
    return NULL;
  }

  return FileInfo->FileName;
}

/**
  Check if the device path is EFI system Partition.

  @param  DevicePath    The ESP device path.

  @retval TRUE    DevicePath is a device path for ESP.
  @retval FALSE   DevicePath is not a device path for ESP.

**/
BOOLEAN
IsEfiSysPartitionDevicePath (
  EFI_DEVICE_PATH_PROTOCOL   *DevicePath
  )
{
  EFI_STATUS                 Status;
  EFI_DEVICE_PATH_PROTOCOL   *TempDevicePath;
  HARDDRIVE_DEVICE_PATH      *Hd;
  EFI_HANDLE                 Handle;

  //
  // Check if the device path contains GPT node
  //
  TempDevicePath = DevicePath;

  while (!IsDevicePathEnd (TempDevicePath)) {
    if ((DevicePathType (TempDevicePath) == MEDIA_DEVICE_PATH) &&
      (DevicePathSubType (TempDevicePath) == MEDIA_HARDDRIVE_DP)) {
      Hd = (HARDDRIVE_DEVICE_PATH *)TempDevicePath;
      if (Hd->MBRType == MBR_TYPE_EFI_PARTITION_TABLE_HEADER) {
        break;
      }
    }
    TempDevicePath = NextDevicePathNode (TempDevicePath);
  }

  if (!IsDevicePathEnd (TempDevicePath)) {
    //
    // Search for EFI system partition protocol on full device path in Boot Option
    //
    Status = gBS->LocateDevicePath (&gEfiPartTypeSystemPartGuid, &DevicePath, &Handle);
    return EFI_ERROR (Status) ? FALSE : TRUE;
  } else {
    return FALSE;
  }
}

/**
  Dump all EFI System Partition.

**/
VOID
DumpAllEfiSysPartition (
  VOID
  )
{
  EFI_HANDLE                 *SimpleFileSystemHandles;
  UINTN                      NumberSimpleFileSystemHandles;
  UINTN                      Index;
  EFI_DEVICE_PATH_PROTOCOL   *DevicePath;
  UINTN                      NumberEfiSystemPartitions;
  EFI_SHELL_PROTOCOL         *ShellProtocol;

  NumberEfiSystemPartitions = 0;

  ShellProtocol = GetShellProtocol ();
  if (ShellProtocol == NULL) {
    Print (L"Get Shell Protocol Fail\n");;
    return ;
  }

  Print (L"EFI System Partition list:\n");

  gBS->LocateHandleBuffer (
         ByProtocol,
         &gEfiSimpleFileSystemProtocolGuid,
         NULL,
         &NumberSimpleFileSystemHandles,
         &SimpleFileSystemHandles
         );

  for (Index = 0; Index < NumberSimpleFileSystemHandles; Index++) {
    DevicePath = DevicePathFromHandle (SimpleFileSystemHandles[Index]);
    if (IsEfiSysPartitionDevicePath (DevicePath)) {
      NumberEfiSystemPartitions++;
      Print(L"    %s\n        %s\n", ShellProtocol->GetMapFromDevicePath (&DevicePath), ConvertDevicePathToText (DevicePath, TRUE, TRUE));
    }
  }

  if (NumberEfiSystemPartitions == 0) {
    Print(L"    No ESP found.\n");
  }
}

/**
  Check if capsule is provisioned.

  @retval TRUE    Capsule is provisioned previously.
  @retval FALSE   No capsule is provisioned.

**/
BOOLEAN
IsCapsuleProvisioned (
  VOID
  )
{
  EFI_STATUS            Status;
  UINT64                OsIndication;
  UINTN                 DataSize;

  OsIndication = 0;
  DataSize = sizeof(UINT64);
  Status = gRT->GetVariable (
                  L"OsIndications",
                  &gEfiGlobalVariableGuid,
                  NULL,
                  &DataSize,
                  &OsIndication
                  );
  if (!EFI_ERROR (Status) &&
      (OsIndication & EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED) != 0) {
    return TRUE;
  }

  return FALSE;
}

/**
  Get one active Efi System Partition.

  @param[out] FsDevicePath   The device path of Fs
  @param[out] Fs             The file system within EfiSysPartition

  @retval EFI_SUCCESS     Get file system successfully
  @retval EFI_NOT_FOUND   No valid file system found

**/
EFI_STATUS
GetEfiSysPartition (
  OUT EFI_DEVICE_PATH_PROTOCOL         **FsDevicePath,
  OUT EFI_SIMPLE_FILE_SYSTEM_PROTOCOL  **Fs
  )
{
  EFI_HANDLE                 *SimpleFileSystemHandles;
  UINTN                      NumberSimpleFileSystemHandles;
  UINTN                      Index;
  EFI_DEVICE_PATH_PROTOCOL   *DevicePath;
  EFI_STATUS                 Status;

  Status = gBS->LocateHandleBuffer (
                  ByProtocol,
                  &gEfiSimpleFileSystemProtocolGuid,
                  NULL,
                  &NumberSimpleFileSystemHandles,
                  &SimpleFileSystemHandles
                  );

  if (EFI_ERROR (Status)) {
    return EFI_NOT_FOUND;
  }

  for (Index = 0; Index < NumberSimpleFileSystemHandles; Index++) {
    DevicePath = DevicePathFromHandle (SimpleFileSystemHandles[Index]);
    if (IsEfiSysPartitionDevicePath (DevicePath)) {
      Status = gBS->HandleProtocol (SimpleFileSystemHandles[Index], &gEfiSimpleFileSystemProtocolGuid, (VOID **)Fs);
      if (!EFI_ERROR (Status)) {
        *FsDevicePath = DevicePath;
        return EFI_SUCCESS;
      }
    }
  }

  return EFI_NOT_FOUND;
}

/**
  Check if Active Efi System Partition within GPT is in the device path.

  @param[in]  DevicePath     The device path
  @param[out] FsDevicePath   The device path of Fs
  @param[out] Fs             The file system within EfiSysPartition

  @retval EFI_SUCCESS    Get file system successfully
  @retval EFI_NOT_FOUND  No valid file system found
  @retval others         Get file system failed

**/
EFI_STATUS
GetEfiSysPartitionFromDevPath (
  IN  EFI_DEVICE_PATH_PROTOCOL        *DevicePath,
  OUT EFI_DEVICE_PATH_PROTOCOL        **FsDevicePath,
  OUT EFI_SIMPLE_FILE_SYSTEM_PROTOCOL **Fs
  )
{
  EFI_STATUS                 Status;
  EFI_DEVICE_PATH_PROTOCOL   *TempDevicePath;
  HARDDRIVE_DEVICE_PATH      *Hd;
  EFI_HANDLE                 Handle;

  //
  // Check if the device path contains GPT node
  //
  TempDevicePath = DevicePath;
  while (!IsDevicePathEnd (TempDevicePath)) {
    if ((DevicePathType (TempDevicePath) == MEDIA_DEVICE_PATH) &&
       (DevicePathSubType (TempDevicePath) == MEDIA_HARDDRIVE_DP)) {
      Hd = (HARDDRIVE_DEVICE_PATH *)TempDevicePath;
      if (Hd->MBRType == MBR_TYPE_EFI_PARTITION_TABLE_HEADER) {
        break;
      }
    }
    TempDevicePath = NextDevicePathNode (TempDevicePath);
  }

  if (!IsDevicePathEnd (TempDevicePath)) {
    //
    // Search for EFI system partition protocol on full device path in Boot Option
    //
    Status = gBS->LocateDevicePath (&gEfiPartTypeSystemPartGuid, &DevicePath, &Handle);

    //
    // Search for simple file system on this handler
    //
    if (!EFI_ERROR (Status)) {
      Status = gBS->HandleProtocol (Handle, &gEfiSimpleFileSystemProtocolGuid, (VOID **)Fs);
      if (!EFI_ERROR (Status)) {
        *FsDevicePath = DevicePathFromHandle (Handle);
        return EFI_SUCCESS;
      }
    }
  }

  return EFI_NOT_FOUND;
}

/**
  Get SimpleFileSystem from boot option file path.

  @param[in]  DevicePath     The file path of boot option
  @param[out] FullPath       The full device path of boot device
  @param[out] Fs             The file system within EfiSysPartition

  @retval EFI_SUCCESS    Get file system successfully
  @retval EFI_NOT_FOUND  No valid file system found
  @retval others         Get file system failed

**/
EFI_STATUS
EFIAPI
GetEfiSysPartitionFromBootOptionFilePath (
  IN  EFI_DEVICE_PATH_PROTOCOL         *DevicePath,
  OUT EFI_DEVICE_PATH_PROTOCOL         **FullPath,
  OUT EFI_SIMPLE_FILE_SYSTEM_PROTOCOL  **Fs
  )
{
  EFI_STATUS                        Status;
  EFI_DEVICE_PATH_PROTOCOL          *CurFullPath;
  EFI_DEVICE_PATH_PROTOCOL          *PreFullPath;
  EFI_DEVICE_PATH_PROTOCOL          *FsFullPath;

  CurFullPath = NULL;
  FsFullPath = NULL;
  //
  // Try every full device Path generated from bootoption
  //
  do {
    PreFullPath = CurFullPath;
    CurFullPath = EfiBootManagerGetNextLoadOptionDevicePath (DevicePath, CurFullPath);

    if (PreFullPath != NULL) {
      FreePool (PreFullPath);
    }

    if (CurFullPath == NULL) {
      //
      // No Active EFI system partition is found in BootOption device path
      //
      Status = EFI_NOT_FOUND;
      break;
    }

    DEBUG_CODE (
      CHAR16 *DevicePathStr;

      DevicePathStr = ConvertDevicePathToText (CurFullPath, TRUE, TRUE);
      if (DevicePathStr != NULL){
        DEBUG ((DEBUG_INFO, "Full device path %s\n", DevicePathStr));
        FreePool (DevicePathStr);
      }
    );

    Status = GetEfiSysPartitionFromDevPath (CurFullPath, &FsFullPath, Fs);
  } while (EFI_ERROR (Status));

  if (*Fs != NULL) {
    *FullPath = FsFullPath;
    return EFI_SUCCESS;
  } else {
    return EFI_NOT_FOUND;
  }
}

/**
  Get a valid SimpleFileSystem within EFI system partition.

  @param[in]  Map             The FS mapping capsule write to
  @param[out] BootNext        The value of BootNext Variable
  @param[out] Fs              The file system within EfiSysPartition
  @param[out] UpdateBootNext  The flag to indicate whether update BootNext Variable

  @retval EFI_SUCCESS    Get FS successfully
  @retval EFI_NOT_FOUND  No valid FS found
  @retval others         Get FS failed

**/
EFI_STATUS
EFIAPI
GetUpdateFileSystem (
  IN  CHAR16                           *Map,
  OUT UINT16                           *BootNext,
  OUT EFI_SIMPLE_FILE_SYSTEM_PROTOCOL  **Fs,
  OUT BOOLEAN                          *UpdateBootNext
)
{
  EFI_STATUS                      Status;
  CHAR16                          BootOptionName[20];
  UINTN                           Index;
  CONST EFI_DEVICE_PATH_PROTOCOL  *MappedDevicePath;
  EFI_DEVICE_PATH_PROTOCOL        *DevicePath;
  EFI_DEVICE_PATH_PROTOCOL        *FullPath;
  UINT16                          *BootNextData;
  EFI_BOOT_MANAGER_LOAD_OPTION    BootNextOption;
  EFI_BOOT_MANAGER_LOAD_OPTION    *BootOptionBuffer;
  UINTN                           BootOptionCount;
  EFI_SHELL_PROTOCOL              *ShellProtocol;
  EFI_BOOT_MANAGER_LOAD_OPTION    NewOption;

  MappedDevicePath = NULL;
  BootOptionBuffer = NULL;

  ShellProtocol = GetShellProtocol ();
  if (ShellProtocol == NULL) {
    Print (L"Get Shell Protocol Fail\n");;
    return EFI_NOT_FOUND;
  }

  //
  // 1. If Fs is not assigned and there are capsule provisioned before,
  // Get EFI system partition from BootNext.
  //
  if (IsCapsuleProvisioned () && Map == NULL) {
    Status = GetVariable2 (
               L"BootNext",
               &gEfiGlobalVariableGuid,
               (VOID **)&BootNextData,
               NULL
               );
    if (EFI_ERROR (Status) || BootNextData == NULL) {
      Print (L"Get Boot Next Data Fail. Status = %r\n", Status);
      return EFI_NOT_FOUND;
    } else {
      UnicodeSPrint (BootOptionName, sizeof (BootOptionName), L"Boot%04x", *BootNextData);
      Status = EfiBootManagerVariableToLoadOption (BootOptionName, &BootNextOption);
      if (!EFI_ERROR (Status)) {
        DevicePath = BootNextOption.FilePath;
        Status = GetEfiSysPartitionFromBootOptionFilePath (DevicePath, &FullPath, Fs);
        if (!EFI_ERROR (Status)) {
          *UpdateBootNext = FALSE;
          Print(L"Get EFI system partition from BootNext : %s\n", BootNextOption.Description);
          Print(L"%s %s\n", ShellProtocol->GetMapFromDevicePath (&FullPath), ConvertDevicePathToText (FullPath, TRUE, TRUE));
          return EFI_SUCCESS;
        }
      }
    }
  }

  //
  // Check if Map is valid.
  //
  if (Map != NULL) {
    MappedDevicePath = ShellProtocol->GetDevicePathFromMap (Map);
    if (MappedDevicePath == NULL) {
      Print(L"'%s' is not a valid mapping.\n", Map);
      return EFI_INVALID_PARAMETER;
    } else if (!IsEfiSysPartitionDevicePath (DuplicateDevicePath (MappedDevicePath))) {
      Print(L"'%s' is not a EFI System Partition.\n", Map);
      return EFI_INVALID_PARAMETER;
    }
  }

  //
  // 2. Get EFI system partition form boot options.
  //
  BootOptionBuffer = EfiBootManagerGetLoadOptions (&BootOptionCount, LoadOptionTypeBoot);
  if ( (BootOptionBuffer == NULL) ||
       (BootOptionCount == 0 && Map == NULL)
     ) {
    return EFI_NOT_FOUND;
  }

  for (Index = 0; Index < BootOptionCount; Index++) {
    //
    // Get the boot option from the link list
    //
    DevicePath  = BootOptionBuffer[Index].FilePath;

    //
    // Skip inactive or legacy boot options
    //
    if ((BootOptionBuffer[Index].Attributes & LOAD_OPTION_ACTIVE) == 0 ||
        DevicePathType (DevicePath) == BBS_DEVICE_PATH) {
      continue;
    }

    DEBUG_CODE (
      CHAR16 *DevicePathStr;

      DevicePathStr = ConvertDevicePathToText (DevicePath, TRUE, TRUE);
      if (DevicePathStr != NULL){
        DEBUG ((DEBUG_INFO, "Try BootOption %s\n", DevicePathStr));
        FreePool (DevicePathStr);
      } else {
        DEBUG ((DEBUG_INFO, "DevicePathToStr failed\n"));
      }
    );

    Status = GetEfiSysPartitionFromBootOptionFilePath (DevicePath, &FullPath, Fs);
    if (!EFI_ERROR (Status)) {
      if (Map == NULL) {
        *BootNext = (UINT16) BootOptionBuffer[Index].OptionNumber;
        *UpdateBootNext = TRUE;
        Print (L"Found EFI system partition on Boot%04x: %s\n", *BootNext, BootOptionBuffer[Index].Description);
        Print (L"%s %s\n", ShellProtocol->GetMapFromDevicePath (&FullPath), ConvertDevicePathToText (FullPath, TRUE, TRUE));
        return EFI_SUCCESS;
      }

      if (StrnCmp (Map, ShellProtocol->GetMapFromDevicePath (&FullPath), StrLen (Map)) == 0) {
        *BootNext = (UINT16) BootOptionBuffer[Index].OptionNumber;
        *UpdateBootNext = TRUE;
        Print (L"Found Boot Option on %s : %s\n", Map, BootOptionBuffer[Index].Description);
        return EFI_SUCCESS;
      }
    }
  }

  //
  // 3. If no ESP is found on boot option, try to find a ESP and create boot option for it.
  //
  if (Map != NULL) {
    //
    // If map is assigned, try to get ESP from mapped Fs.
    //
    DevicePath = DuplicateDevicePath (MappedDevicePath);
    Status = GetEfiSysPartitionFromDevPath (DevicePath, &FullPath, Fs);
    if (EFI_ERROR (Status)) {
      Print (L"Error: Cannot get EFI system partiion from '%s' - %r\n", Map, Status);
      return EFI_NOT_FOUND;
    }
    Print (L"Warning: Cannot find Boot Option on '%s'!\n", Map);
  } else {
    Status = GetEfiSysPartition (&DevicePath, Fs);
    if (EFI_ERROR (Status)) {
      Print (L"Error: Cannot find a EFI system partition!\n");
      return EFI_NOT_FOUND;
    }
  }

  Print (L"Create Boot option for capsule on disk:\n");
  Status = EfiBootManagerInitializeLoadOption (
             &NewOption,
             LoadOptionNumberUnassigned,
             LoadOptionTypeBoot,
             LOAD_OPTION_ACTIVE,
             L"UEFI Capsule On Disk",
             DevicePath,
             (UINT8 *) &mCapsuleOnDiskBootOptionGuid,
             sizeof(EFI_GUID)
             );
  if (!EFI_ERROR (Status)) {
    Status = EfiBootManagerAddLoadOptionVariable (&NewOption, (UINTN) -1); {
      if (!EFI_ERROR (Status)) {
        *UpdateBootNext = TRUE;
        *BootNext = (UINT16) NewOption.OptionNumber;
        Print (L"  Boot%04x: %s\n", *BootNext, ConvertDevicePathToText(DevicePath, TRUE, TRUE));
        return EFI_SUCCESS;
      }
    }
  }

  Print (L"ERROR: Cannot create boot option! - %r\n", Status);

  return EFI_NOT_FOUND;
}

/**
  Write files to a given SimpleFileSystem.

  @param[in] Buffer          The buffer array
  @param[in] BufferSize      The buffer size array
  @param[in] FileName        The file name array
  @param[in] BufferNum       The buffer number
  @param[in] Fs              The SimpleFileSystem handle to be written

  @retval EFI_SUCCESS    Write file successfully
  @retval EFI_NOT_FOUND  SFS protocol not found
  @retval others         Write file failed

**/
EFI_STATUS
WriteUpdateFile (
  IN  VOID                                 **Buffer,
  IN  UINTN                                *BufferSize,
  IN  CHAR16                               **FileName,
  IN  UINTN                                BufferNum,
  IN  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL      *Fs
)
{
  EFI_STATUS                          Status;
  EFI_FILE                            *Root;
  EFI_FILE                            *FileHandle;
  EFI_FILE_PROTOCOL                   *DirHandle;
  UINT64                              FileInfo;
  VOID                                *Filebuffer;
  UINTN                               FileSize;
  UINTN                               Index;

  DirHandle   = NULL;
  FileHandle  = NULL;
  Index       = 0;

  //
  // Open Root from SFS
  //
  Status = Fs->OpenVolume (Fs, &Root);
  if (EFI_ERROR (Status)) {
    Print (L"Cannot open volume. Status = %r\n", Status);
    return EFI_NOT_FOUND;
  }

  //
  // Ensure that efi and updatecapsule directories exist
  //
  Status = Root->Open (Root, &DirHandle, L"\\EFI", EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE, 0);
  if (EFI_ERROR (Status)) {
    Status = Root->Open (Root, &DirHandle, L"\\EFI", EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, EFI_FILE_DIRECTORY);
    if (EFI_ERROR (Status)) {
      Print(L"Unable to create %s directory\n", L"\\EFI");
      return EFI_NOT_FOUND;
    }
  }
  Status = Root->Open (Root, &DirHandle, EFI_CAPSULE_FILE_DIRECTORY, EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE , 0);
  if (EFI_ERROR (Status)) {
    Status = Root->Open (Root, &DirHandle, EFI_CAPSULE_FILE_DIRECTORY, EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, EFI_FILE_DIRECTORY);
    if (EFI_ERROR (Status)) {
      Print(L"Unable to create %s directory\n", EFI_CAPSULE_FILE_DIRECTORY);
      return EFI_NOT_FOUND;
    }
  }

  for (Index = 0; Index < BufferNum; Index++) {
    FileHandle = NULL;

    //
    // Open UpdateCapsule file
    //
    Status = DirHandle->Open (DirHandle, &FileHandle, FileName[Index], EFI_FILE_MODE_CREATE | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_READ, 0);
    if (EFI_ERROR (Status)) {
      Print (L"Unable to create %s file\n", FileName[Index]);
      return EFI_NOT_FOUND;
    }

    //
    // Empty the file contents
    //
    Status = FileHandleGetSize (FileHandle, &FileInfo);
    if (EFI_ERROR (Status)) {
      FileHandleClose (FileHandle);
      Print (L"Error Reading %s\n", FileName[Index]);
      return EFI_DEVICE_ERROR;
    }

    //
    // If the file size is already 0, then it has been empty.
    //
    if (FileInfo != 0) {
      //
      // Set the file size to 0.
      //
      FileInfo = 0;
      Status = FileHandleSetSize (FileHandle, FileInfo);
      if (EFI_ERROR (Status)) {
        Print (L"Error Deleting %s\n", FileName[Index]);
        FileHandleClose (FileHandle);
        return Status;
      }
    }

    //
    // Write Filebuffer to file
    //
    Filebuffer = Buffer[Index];
    FileSize = BufferSize[Index];
    Status = FileHandleWrite (FileHandle, &FileSize, Filebuffer);
    if (EFI_ERROR (Status)) {
      Print (L"Unable to write Capsule Update to %s, Status = %r\n", FileName[Index], Status);
      return EFI_NOT_FOUND;
    }

    Print (L"Succeed to write %s\n", FileName[Index]);
    FileHandleClose (FileHandle);
  }

  return EFI_SUCCESS;
}

/**
  Set capsule status variable.

  @param[in] SetCap     Set or clear the capsule flag.

  @retval EFI_SUCCESS   Succeed to set SetCap variable.
  @retval others        Fail to set the variable.

**/
EFI_STATUS
SetCapsuleStatusVariable (
  BOOLEAN                       SetCap
  )
{
  EFI_STATUS                    Status;
  UINT64                        OsIndication;
  UINTN                         DataSize;

  OsIndication = 0;
  DataSize = sizeof(UINT64);
  Status = gRT->GetVariable (
                  L"OsIndications",
                  &gEfiGlobalVariableGuid,
                  NULL,
                  &DataSize,
                  &OsIndication
                  );
  if (EFI_ERROR (Status)) {
    OsIndication = 0;
  }
  if (SetCap) {
    OsIndication |= ((UINT64)EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED);
  }
  else {
    OsIndication &= ~((UINT64)EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED);
  }
  Status = gRT->SetVariable (
                  L"OsIndications",
                  &gEfiGlobalVariableGuid,
                  EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE,
                  sizeof(UINT64),
                  &OsIndication
                  );

  return Status;
}

/**
  Process Capsule On Disk.

  @param[in]  CapsuleBuffer       An array of pointer to capsule images
  @param[in]  CapsuleBufferSize   An array of UINTN to capsule images size
  @param[in]  FilePath            An array of capsule images file path
  @param[in]  Map                 File system mapping string
  @param[in]  CapsuleNum          The count of capsule images

  @retval EFI_SUCCESS       Capsule on disk success.
  @retval others            Capsule on disk fail.

**/
EFI_STATUS
ProcessCapsuleOnDisk (
  IN VOID                          **CapsuleBuffer,
  IN UINTN                         *CapsuleBufferSize,
  IN CHAR16                        **FilePath,
  IN CHAR16                        *Map,
  IN UINTN                         CapsuleNum
  )
{
  EFI_STATUS                      Status;
  UINT16                          BootNext;
  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Fs;
  BOOLEAN                         UpdateBootNext;

  //
  // Get a valid file system from boot path
  //
  Fs = NULL;

  Status = GetUpdateFileSystem (Map, &BootNext, &Fs, &UpdateBootNext);
  if (EFI_ERROR (Status)) {
    Print (L"CapsuleApp: cannot find a valid file system on boot devies. Status = %r\n", Status);
    return Status;
  }

  //
  // Copy capsule image to '\efi\UpdateCapsule\'
  //
  Status = WriteUpdateFile (CapsuleBuffer, CapsuleBufferSize, FilePath, CapsuleNum, Fs);
  if (EFI_ERROR (Status)) {
    Print (L"CapsuleApp: capsule image could not be copied for update.\n");
    return Status;
  }

  //
  // Set variable then reset
  //
  Status = SetCapsuleStatusVariable (TRUE);
  if (EFI_ERROR (Status)) {
    Print (L"CapsuleApp: unable to set OSIndication variable.\n");
    return Status;
  }

  if (UpdateBootNext) {
    Status = gRT->SetVariable (
      L"BootNext",
      &gEfiGlobalVariableGuid,
      EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE,
      sizeof(UINT16),
      &BootNext
      );
    if (EFI_ERROR (Status)){
      Print (L"CapsuleApp: unable to set BootNext variable.\n");
      return Status;
    }
  }

  return EFI_SUCCESS;
}
